import time
from typing import List
from fastapi import Request
from tortoise.expressions import Q
from hypertext import PagingResult
from exception import AppException
from common.utils.urls import UrlUtil
from common.utils.times import TimeUtil
from common.utils.config import ConfigUtil
from common.enums.apply import MusicEnum
from common.models.users import UserModel
from common.models.music import MusicSquareModel
from common.models.music import MusicCollectModel
from common.models.music import MusicStylesModel
from common.models.music import MusicRecordsModel
from common.chain.music_server import MusicOpenAiHkServer
from common.service.subscribe_logic import SubscribeLogic
from plugins.censor.driver import CensorDriver
from apps.api.schemas.apply import music_schema as schema


class MusicBasicService:

    @classmethod
    async def config(cls, user_id: int, engine: str) -> schema.MusicConfigVo:
        """
        音乐配置参数

        Returns:
           List[schema.MusicConfigVo]

        Author:
           zero
        """
        options = []
        configs = {
            "status": False,
            "points": 0,
            "surplus_num": 0,
            "free_status": False
        }

        # 订阅特权
        member = await SubscribeLogic.get_surplus_free(user_id=user_id, scene="music", mark=engine)
        if member:
            configs["surplus_num"] = member.get("surplus_num")
            configs["free_status"] = member.get("free_status")

        # SUNO配置
        suno = await ConfigUtil.get("music", "suno") or {}
        if suno.get("status"):
            name = suno.get("name") or "Suno"
            options.append({"type": "suno", "name": name})

        # 场景处理
        if engine == MusicEnum.ENGINE_SUNO:
            configs["status"] = bool(suno.get("status"))
            configs["points"] = int(suno.get("points", 0))

        return schema.MusicConfigVo(
            options=options,
            configs=configs
        )

    @classmethod
    async def styles(cls) -> List[schema.MusicStylesVo]:
        """
        音乐风格列表

        Returns:
            List[schema.MusicStylesVo]

        Author:
            zero
        """
        lists = await (MusicStylesModel
                       .filter(is_delete=0)
                       .filter(is_disable=0)
                       .limit(200)
                       .order_by("-sort", "-id")
                       .all())

        _lists = []
        for item in lists:
            _lists.append(schema.MusicStylesVo(
                id=item.id,
                image=await UrlUtil.to_absolute_url(item.image),
                name=item.name,
                value=item.value,
            ))

        return _lists

    @classmethod
    async def record(cls, user_id: int, params: schema.MusicRecordSearchIn) -> PagingResult[schema.MusicRecordsVo]:
        """
        音乐记录列表

        Args:
            user_id (int): 用户ID
            params (schema.MusicRecordSearchIn): 搜索参数

        Author:
            zero
        """
        # 查询条件
        where = [Q(user_id=user_id), Q(is_delete=0), Q(engine=params.engine)]
        if params.status and int(params.status) > int(0):
            where.append(Q(status=int(params.status)))

        # 查询数据
        _model = MusicRecordsModel.filter(*where).order_by(str("-id"))
        _pager = await MusicRecordsModel.paginate(
            model=_model,
            page_no=params.page_no,
            page_size=params.page_size
        )

        # 广场查询
        record_ids = [item[str("id")] for item in _pager.lists]
        squares = await MusicSquareModel.querySquareDictsByRecordId(record_ids)

        _lists = []
        for item in _pager.lists:
            # 广场状态: [-1=未分享,-2=被隐藏,0=待审核,1=通过,2=拒绝]
            square = squares.get(item["id"], {})
            share_status = square.get("audit_status", -1)
            if share_status == 0 and square.get("is_enable") == 0:
                share_status = - 2

            _lists.append(schema.MusicRecordsVo(
                id=item["id"],
                code=item["code"],
                title=item["title"],
                prompt=item["prompt"],
                tags=item["tags"],
                make_instrumental=bool(item["make_instrumental"]),
                cover_url=await UrlUtil.to_absolute_url(item["cover_url"]),
                audio_url=await UrlUtil.to_absolute_url(item["audio_url"]),
                duration=item["duration"],
                lyric=item["lyric"] or "",
                is_free=item["is_free"],
                share_status=share_status,
                share_result=square.get("audit_result") or "",
                fail_reason=item["fail_reason"] or "",
                status=item["status"],
                use_points=item["use_points"],
                create_time=item["create_time"]
            ))

        _pager.lists = _lists
        return _pager

    @classmethod
    async def detection(cls, user_id: int, ids: List[str]) -> List[schema.MusicRecordsVo]:
        """
        音乐记录检测

        Args:
            user_id (int): 用户ID
            ids (List[str]): 音乐记录编号列表

        Returns:
            List[schema.MusicRecordsVo]

        Author:
            zero
        """
        if len(ids) <= 0:
            return []

        lists = await (MusicRecordsModel
                       .filter(user_id=user_id)
                       .filter(is_delete=0)
                       .filter(code__in=ids)
                       .limit(100)
                       .all())

        # 广场查询
        record_ids = [item.id for item in lists]
        squares = await MusicSquareModel.querySquareDictsByRecordId(record_ids)

        # 处理列表
        _lists = []
        for item in lists:
            # 广场状态: [-1=未分享,-2=被隐藏,0=待审核,1=通过,2=拒绝]
            square = squares.get(item.id, {})
            share_status = square.get("audit_status", -1)
            if share_status == 0 and square.get("is_enable") == 0:
                share_status = - 2

            _lists.append(schema.MusicRecordsVo(
                id=item.id,
                code=item.code,
                title=item.title,
                prompt=item.prompt,
                tags=item.tags,
                make_instrumental=bool(item.make_instrumental),
                cover_url=await UrlUtil.to_absolute_url(item.cover_url),
                audio_url=await UrlUtil.to_absolute_url(item.audio_url),
                duration=item.duration,
                is_free=item.is_free,
                share_status=share_status,
                share_result=square.get("audit_result") or "",
                fail_reason=item.fail_reason or "",
                lyric=item.lyric or "",
                status=item.status,
                use_points=item.use_points,
                create_time=TimeUtil.timestamp_to_date(item.create_time)
            ))

        return _lists

    @classmethod
    async def delete(cls, user_id: int, code: str) -> None:
        """
        音乐记录删除

        Args:
            user_id (int): 用户ID
            code (str): 音乐记录编号

        Author:
            zero
        """
        record = await (MusicRecordsModel
                        .filter(code=code)
                        .filter(user_id=user_id)
                        .filter(is_delete=0)
                        .get())

        record.is_delete = 1
        record.delete_time = int(time.time())
        await record.save()

    @classmethod
    async def square(cls, user_id: int, code: str) -> None:
        """
        音乐广场公开

        Args:
            user_id (int): 用户ID
            code (str): 记录编号

        Author:
            zero
        """
        record = await MusicRecordsModel.filter(user_id=user_id, code=code).first()
        if record is None:
            raise AppException("音乐记录不存在")

        square = await MusicSquareModel.filter(record_id=record.id).first()
        if record.is_delete:
            if square is None or square.is_delete:
                raise AppException("音乐记录不存在")

        if square is None:
            config = await ConfigUtil.get("square", "music", {})
            audit_type = int(config.get("audit_type", 0))
            await MusicSquareModel.create(
                source=2,
                cate_id=0,
                operate_id=user_id,
                record_id=record.id,
                music_code=record.code,
                cover_url=record.cover_url,
                audio_url=record.audio_url,
                title=record.title,
                tags=record.tags,
                prompt=record.prompt,
                lyric=record.lyric,
                duration=record.duration,
                public_sum=1,
                audit_result="",
                audit_status=audit_type,
                is_enable=1,
                create_time=int(time.time()),
                update_time=int(time.time())
            )
        else:
            if square.is_delete:
                await MusicSquareModel.filter(id=square.id).update(
                    public_sum=square.public_sum + 1,
                    is_delete=0,
                    delete_time=0
                )
            else:
                await MusicSquareModel.filter(id=square.id).update(
                    is_delete=1,
                    delete_time=int(time.time())
                )

    @classmethod
    async def collect(cls, user_id: int, code: str) -> None:
        """
        音乐记录收藏

        Args:
            user_id (int): 用户ID
            code (str): 记录编号

        Author:
            zero
        """
        record = await MusicRecordsModel.filter(user_id=user_id, code=code).first()
        if record is None:
            raise AppException("音乐记录不存在")

        collect = await MusicCollectModel.filter(user_id=user_id, record_id=record.id).first()
        square = await MusicSquareModel.filter(record_id=record.id).first()
        if square is None:
            if collect is None or collect.is_delete:
                raise AppException("音乐记录不存在")

        if collect is None:
            await MusicCollectModel.create(
                user_id=user_id,
                record_id=record.id,
                total_num=int(1),
                create_time=int(time.time()),
                update_time=int(time.time())
            )
        else:
            if collect.is_delete:
                await MusicCollectModel.filter(id=collect.id).update(
                    total_num=collect.total_num + 1,
                    is_delete=0,
                    delete_time=0
                )
            else:
                await MusicCollectModel.filter(id=collect.id).update(
                    is_delete=1,
                    delete_time=int(time.time())
                )


class MusicGenService:
    def __init__(self, request: Request, user_id: int, params: schema.MusicGenerateIn):
        self.request = request
        self.user_id = user_id
        self.params = params
        self.user = None
        self.engine = params.engine
        self.channel = ""
        self.points: int = 0
        self.vip_free: bool = False

    async def generate(self):
        # 验证参数
        await self._initialize()

        # 验证费用
        await self._check_charging()

        # 违规审查
        review = CensorDriver.REVIEW_MUSIC
        censor = await CensorDriver.text(user_id=self.user_id, text=self.params.prompt, scene=review)
        if censor is not None:
            raise AppException(f"描述词违规: {censor}")

        # 音乐风格
        if self.params.styles:
            styles = await MusicStylesModel.filter(id__in=self.params.styles).values("value")
            styles = ",".join([item["value"] for item in styles])
            self.params.tags = styles + "," + self.params.tags

        # 发起请求
        try:
            if self.params.engine == MusicEnum.ENGINE_SUNO:
                music_server = MusicOpenAiHkServer()
                response = await music_server.submit({
                    "title": self.params.title,
                    "prompt": self.params.prompt,
                    "tags": self.params.tags,
                    "mv": self.params.mv,
                    "make_instrumental": self.params.make_instrumental
                })
            else:
                raise Exception("Engine not found")

            await self._save_record(response)
        except Exception as e:
            raise AppException(str(e))

    async def _initialize(self):
        # 渠道状态
        config = await ConfigUtil.get("music") or {}
        config = config.get(self.engine) or {}
        print(config)
        if not config.get("status"):
            raise AppException("当前音乐渠道已关闭")
        if not config or not isinstance(config, dict):
            raise AppException("当前音乐渠道配置异常")

        # 获取配置
        self.channel = str(config.get("channel", ""))
        self.points = int(config.get("points", 0))

        # 获取用户
        self.user = await UserModel.filter(id=self.user_id).first()
        if not self.user:
            raise AppException("用户信息错误")

    async def _check_charging(self):
        """ 验证费用 """
        # 验证通道状态
        config = await ConfigUtil.get("video", self.params.engine) or {}
        if not config.get("status"):
            AppException("当前视频渠道已关闭")

        # 获取渠道数据
        self.points = int(config.get("points", "0"))
        self.channel = config.get("channel", "")

        # 获取订阅会员
        member = await SubscribeLogic.get_surplus_free(user_id=self.user_id, scene="music", mark=self.params.engine)
        if member:
            self.vip_free = member.get("free_status")

        # 验证账户余额
        if not self.vip_free and self.user.points < self.points:
            raise AppException("账户余额不足,请充值!")

    async def _save_record(self, response: dict):
        """ 保存记录 """
        for clip in response.get("clips", []):
            await MusicRecordsModel.create(
                user_id=self.user_id,
                task_id=clip["id"],
                engine=MusicEnum.ENGINE_SUNO,
                channel=self.channel,
                mode=self.params.mode,
                title=self.params.title,
                prompt=self.params.prompt,
                tags=self.params.tags,
                mv=self.params.mv,
                make_instrumental=self.params.make_instrumental,
                use_points=self.points,
                status=MusicEnum.STATUS_WAIT,
                ip=self.request.client.host,
                create_time=int(time.time()),
                update_time=int(time.time())
            )
